using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Xml;
using System.IO.Compression;


namespace EuropeEngulfed.NET
{
    class TCPServer
    {
        private delegate void EEMainFormQueueMessagesCallback(EuropeEngulfedNETForm form, List<string> listMessages);
        private delegate List<string> EEMainFormGetMessagesCallback(EuropeEngulfedNETForm form);
        public const int EENET_Port = 5271;
        public const int EENET_InformationPort = 5272;
        private Socket acceptedSocket;
        private Socket listenSocket;
        private Socket infoListenSocket;
        private Socket informationSocket;
        public const char EndOfMessage = '@';
        private byte[] receivebuffer;
        private byte[] respondbuffer;

        private byte[] receiveInfobuffer;
        private byte[] respondInfobuffer;

        bool connected = false;
        private GameController currentGameController;
        private ServerMessage serverMessageToSend;
        private List<ServerRequestMessage> clientResponsesExpected;
        private ClientMessage clientMessageReceived;
        private bool clientIsExpectingResponse = false;
        private bool exiting = false;

        public ClientMessage ClientMessageReceived
        {
            get
            {
                return clientMessageReceived;
            }
        }

        public bool ClientResponded(ServerRequestMessage clientResponseExpected)
        {
            if (exiting)
                return true;
            if (clientResponsesExpected.Contains(clientResponseExpected))
                return false;
            return true;
        }

        public enum ServerRequestMessage
        {
            Polling,
            GetCurrentGameState,
            AskForDefenderOptionsReturn,
            DoDefenderCombatReturn,
            AskAttackerToAssignLosses,
            AssignLossesReturn,
            CounterAttackReturn,
            StratWarfareReturn,
            DoStrategicUBoatWarfareReturn,
            ProductionMovesReturn,
            boolReturn,
            MovesReturn,
            AskForGSUCombatCasualtiesReturn,
            AskForBreakThroughOptionReturn,
            DetermineStrategicWarInterception,
            AskForNewCountryDeployment,
            AskForNavalInterception,
            ReportForNavalInterception,
            AskDefenderForDefenderOptions,
            AskDefenderForRetreatBeforeCombatMoves,
            AskForGSUCombatCasualties,
            DoDefenderCombat,
            DoDefenderBreakThroughCombat,
            AskDefenderToAssignLosses,
            AskDefenderForCounterAttack,
        };

        public TCPServer(GameController currentGameController)
        {
            clientResponsesExpected = new List<ServerRequestMessage>();
            serverMessageToSend = null;
            this.currentGameController = currentGameController;
            receivebuffer = new byte[204800];
            respondbuffer = new byte[204800];

            receiveInfobuffer = new byte[500];
            respondInfobuffer = new byte[500];

            // create the socket
            listenSocket = new Socket(AddressFamily.InterNetwork,
                                             SocketType.Stream,
                                             ProtocolType.Tcp);

            // bind the listening socket to the port
            IPAddress hostIP = (Dns.Resolve(Dns.GetHostName())).AddressList[0];
            IPEndPoint ep = new IPEndPoint(hostIP, EENET_Port);
            listenSocket.Bind(ep);

            // create the socket
            infoListenSocket = new Socket(AddressFamily.InterNetwork,
                                             SocketType.Stream,
                                             ProtocolType.Tcp);
            IPEndPoint infoep = new IPEndPoint(hostIP, EENET_InformationPort);
            infoListenSocket.Bind(infoep);
        }

        public bool Connected
        {
            get
            {
                return connected;
            }
        }

        public void Start()
        {
            // start listening
            listenSocket.Listen(1);

            listenSocket.BeginAccept(new AsyncCallback(EndAccept), null);
            try
            {
                infoListenSocket.Listen(1);
                infoListenSocket.BeginAccept(new AsyncCallback(EndAcceptInformation), null);
            }
            catch (SocketException e)
            {
                System.Console.WriteLine("Problem with information socket" + e.ToString());
            }
        }

        private int ReadAllBytesFromStream(System.IO.Stream stream, byte[] buffer)
        {
            // Use this method is used to read all bytes from a stream.
            int offset = 0;
            int totalCount = 0;
            while (true)
            {
                int bytesRead = stream.Read(buffer, offset, 100);
                if (bytesRead == 0)
                {
                    break;
                }
                offset += bytesRead;
                totalCount += bytesRead;
            }
            return totalCount;

        }

        private void EndAccept(IAsyncResult ar)
        {
            acceptedSocket = listenSocket.EndAccept(ar);
            connected = true;
            receivebuffer.Initialize();
            acceptedSocket.BeginReceive(receivebuffer, 0, receivebuffer.Length, SocketFlags.None, new AsyncCallback(EndReceive), null);
        }

        private void EndAcceptInformation(IAsyncResult ar)
        {
            try
            {
                informationSocket = infoListenSocket.EndAccept(ar);
                receiveInfobuffer.Initialize();
                informationSocket.BeginReceive(receiveInfobuffer, 0, receiveInfobuffer.Length, SocketFlags.None, new AsyncCallback(EndReceiveInformation), null);
            }
            catch (SocketException e)
            {
                informationSocket.Disconnect(true);
                infoListenSocket.BeginAccept(new AsyncCallback(EndAcceptInformation), null);
                System.Console.WriteLine("Problem with information socket" + e.ToString());
            }
        }

        private void QueueMessagesCallback(EuropeEngulfedNETForm form, List<string> messages)
        {
            form.QueueInfoMessages(messages);
        }

        private List<string> GetMessagesCallback(EuropeEngulfedNETForm form)
        {
            return form.GetInfoMessagesToSend();
        }

        private void EndReceiveInformation(IAsyncResult ar)
        {
            try
            {
                int bytesReceived = informationSocket.EndReceive(ar);
                int bytePosition = 0;
                while (receiveInfobuffer[bytePosition] != 0)
                {
                    bytePosition++;
                    if (bytePosition >= receiveInfobuffer.Length)
                        break;
                }

                bytePosition++;
                if(bytePosition < bytesReceived)
                {
                    List<string> infoMessages = new List<string>();
                    while (bytePosition < bytesReceived)
                    {
                        int bytePositionInString = bytePosition;
                        while (receiveInfobuffer[bytePositionInString] != 0)
                        {
                            bytePositionInString++;
                            if (bytePositionInString >= receiveInfobuffer.Length)
                                break;
                        }
                        string infoMessageString = Encoding.ASCII.GetString(receiveInfobuffer, bytePosition, bytePositionInString - bytePosition);
                        if(infoMessageString.Length > 0)
                            infoMessages.Add(infoMessageString);
                        bytePosition += infoMessageString.Length + 1;
                    }
                    if(infoMessages.Count > 0)
                    {
                        if (EuropeEngulfedNETForm.MainForm.InvokeRequired)
                            EuropeEngulfedNETForm.MainForm.Invoke(new EEMainFormQueueMessagesCallback(QueueMessagesCallback), new object[] { EuropeEngulfedNETForm.MainForm, infoMessages });
                        else
                            EuropeEngulfedNETForm.MainForm.QueueInfoMessages(infoMessages);
                    }
                }

                ClearInfoRespondBuffer();
                string respondString = Enum.GetName(typeof(TCPClient.ClientMessageType), TCPClient.ClientMessageType.OK);
                Encoding.ASCII.GetBytes(respondString, 0, respondString.Length, respondInfobuffer, 0);
                List<string> messagesToSend = null;
                if (EuropeEngulfedNETForm.MainForm.InvokeRequired)
                    messagesToSend = (List<string>)EuropeEngulfedNETForm.MainForm.Invoke(new EEMainFormGetMessagesCallback(GetMessagesCallback), new object[] { EuropeEngulfedNETForm.MainForm });
                else
                    messagesToSend = EuropeEngulfedNETForm.MainForm.GetInfoMessagesToSend();
                int position = respondString.Length + 1;
                foreach(string message in messagesToSend)
                {
                    if ((position + message.Length) > respondInfobuffer.Length)
                        break;
                    Encoding.ASCII.GetBytes(message, 0, message.Length, respondInfobuffer, position);
                    position += message.Length + 1;
                }

                informationSocket.BeginSend(respondInfobuffer, 0, position, SocketFlags.None, new AsyncCallback(EndSendInformation), null);
            }
            catch (SocketException e)
            {
                informationSocket.Disconnect(true);
                infoListenSocket.BeginAccept(new AsyncCallback(EndAcceptInformation), null);
                System.Console.WriteLine("Problem with information socket" + e.ToString());
            }
        }

        private void EndSendInformation(IAsyncResult ar)
        {
            try
            {
                informationSocket.EndSend(ar);
                ClearReceiveInfoBuffer();
                informationSocket.BeginReceive(receiveInfobuffer, 0, receiveInfobuffer.Length, SocketFlags.None, new AsyncCallback(EndReceiveInformation), null);
            }
            catch (SocketException e)
            {
                informationSocket.Disconnect(true);
                infoListenSocket.BeginAccept(new AsyncCallback(EndAcceptInformation), null);
                System.Console.WriteLine("Problem with information socket" + e.ToString());
            }
        }

        private ServerRequestMessage GetServerMessageFromHeader(int bytesReceived, ref int numBytesFirstHeader)
        {
            ServerRequestMessage serverMessageReceived = ServerRequestMessage.Polling;
            if (bytesReceived > 0)
            {
                numBytesFirstHeader = 0;
                while (receivebuffer[numBytesFirstHeader] != 0)
                {
                    numBytesFirstHeader++;
                    if (numBytesFirstHeader >= receivebuffer.Length)
                        break;
                }

                string serverMessageString = Encoding.ASCII.GetString(receivebuffer, 0, numBytesFirstHeader);
                try
                {
                    return (ServerRequestMessage)Enum.Parse(typeof(ServerRequestMessage), serverMessageString);
                }
                catch
                {
                    return ServerRequestMessage.Polling;
                }
            }
            return serverMessageReceived;
        }

        private int ProcessGetCurrentGameState(GameTurn.GameDate gameDateCutoffForMoveHistory)
        {
            ClearRespondBuffer();
            while (!currentGameController.GameStarted)
            {
                System.Threading.Thread.Sleep(500);
            }

            int numBytesReply = 0;
            string respondString = Enum.GetName(typeof(TCPClient.ClientMessageType), TCPClient.ClientMessageType.OK);
            XmlDocument gameState = currentGameController.GetXMLGame(gameDateCutoffForMoveHistory);
            Encoding.ASCII.GetBytes(respondString, 0, respondString.Length, respondbuffer, 0);
            numBytesReply = respondString.Length + 1;

            System.IO.StringWriter sw = new System.IO.StringWriter();
            XmlTextWriter xmlWriter = new XmlTextWriter(sw);
            xmlWriter.Formatting = Formatting.None;
            gameState.WriteTo(xmlWriter);
            Encoding.ASCII.GetBytes(sw.ToString(), 0, sw.ToString().Length, respondbuffer, numBytesReply);
            numBytesReply += sw.ToString().Length;

            return numBytesReply;
        }

        private void ClearInfoRespondBuffer()
        {
            for (int ibyte = 0; ibyte < respondInfobuffer.Length; ibyte++)
            {
                respondInfobuffer[ibyte] = 0;
            }
        }

        private void ClearRespondBuffer()
        {
            for (int ibyte = 0; ibyte < respondbuffer.Length; ibyte++)
            {
                respondbuffer[ibyte] = 0;
            }
        }

        private void ClearReceiveBuffer()
        {
            for (int ibyte = 0; ibyte < receivebuffer.Length; ibyte++)
            {
                respondbuffer[ibyte] = 0;
            }
        }

        private void ClearReceiveInfoBuffer()
        {
            for (int ibyte = 0; ibyte < receiveInfobuffer.Length; ibyte++)
            {
                receiveInfobuffer[ibyte] = 0;
            }
        }

        private int ProcessServerMessageRequest()
        {
            XmlDocument xmlParams = new XmlDocument();
            ClearRespondBuffer();
            while (!currentGameController.GameStarted)
            {
                System.Threading.Thread.Sleep(500);
            }

            int numBytesReply = 0;
            string respondString = Enum.GetName(typeof(TCPClient.ClientMessageType), serverMessageToSend.ClientMessageToReturn);
            xmlParams.AppendChild(serverMessageToSend.Serialise(xmlParams));
            Encoding.ASCII.GetBytes(respondString, 0, respondString.Length, respondbuffer, 0);
            numBytesReply = respondString.Length + 1;

            System.IO.StringWriter sw = new System.IO.StringWriter();
            XmlTextWriter xmlWriter = new XmlTextWriter(sw);
            xmlWriter.Formatting = Formatting.None;
            xmlParams.WriteTo(xmlWriter);
            Encoding.ASCII.GetBytes(sw.ToString(), 0, sw.ToString().Length, respondbuffer, numBytesReply);
            numBytesReply += sw.ToString().Length;

            return numBytesReply;
        }

        private void ProcessClientMessageReturn(ServerRequestMessage serverRequestMessage, int numBytesHeader, int bytesReceived)
        {
            ServerRequestMessage clientResponseExpected;
            if (clientResponsesExpected.Count > 0)
                clientResponseExpected = clientResponsesExpected[clientResponsesExpected.Count - 1];
            else
                clientResponseExpected = ServerRequestMessage.Polling;

            if (serverRequestMessage == clientResponseExpected)
            {
                string XmlString = Encoding.ASCII.GetString(receivebuffer, numBytesHeader + 1, bytesReceived - (numBytesHeader + 1));
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(XmlReader.Create(new System.IO.StringReader(XmlString)));
                this.clientMessageReceived = ClientMessage.Deserialise(xmlDoc.FirstChild);
                if (clientResponsesExpected.Count > 0)
                    clientResponsesExpected.RemoveAt(clientResponsesExpected.Count - 1);
            }
        }

        private void EndReceive(IAsyncResult ar)
        {
            try
            {
                ServerRequestMessage serverRequestMessage;
                int numBytesHeader = 0;
                int numBytesReply = 0;
                string respondString = "";
                int bytesReceived = acceptedSocket.EndReceive(ar);
                ClearRespondBuffer();
                switch ((serverRequestMessage = GetServerMessageFromHeader(bytesReceived, ref numBytesHeader)))
                {
                    case ServerRequestMessage.Polling:
                        if (serverMessageToSend == null)
                        {
                            respondString = Enum.GetName(typeof(TCPClient.ClientMessageType), TCPClient.ClientMessageType.OK);
                            Encoding.ASCII.GetBytes(respondString, 0, respondString.Length, respondbuffer, 0);
                            numBytesReply = respondString.Length + 1;
                        }
                        else
                        {
                            numBytesReply = ProcessServerMessageRequest();
                            serverMessageToSend = null;
                        }
                        break;
                    case ServerRequestMessage.GetCurrentGameState:
                        string gameDateString = Encoding.ASCII.GetString(receivebuffer, numBytesHeader + 1, bytesReceived - (numBytesHeader + 1));
                        if (gameDateString == TCPClient.UNKNOWN_GAME_DATE)
                            numBytesReply = ProcessGetCurrentGameState(currentGameController.GetCurrentGameDate());
                        else
                            numBytesReply = ProcessGetCurrentGameState((GameTurn.GameDate)Enum.Parse(typeof(GameTurn.GameDate), gameDateString));
                        break;

                    case ServerRequestMessage.AssignLossesReturn:
                    case ServerRequestMessage.DoDefenderCombatReturn:
                    case ServerRequestMessage.AskForDefenderOptionsReturn:
                    case ServerRequestMessage.CounterAttackReturn:
                    case ServerRequestMessage.StratWarfareReturn:
                    case ServerRequestMessage.DoStrategicUBoatWarfareReturn:
                    case ServerRequestMessage.ProductionMovesReturn:
                    case ServerRequestMessage.boolReturn:
                    case ServerRequestMessage.MovesReturn:
                    case ServerRequestMessage.AskForGSUCombatCasualtiesReturn:
                    case ServerRequestMessage.AskForBreakThroughOptionReturn:
                        {
                            ProcessClientMessageReturn(serverRequestMessage, numBytesHeader, bytesReceived);
                            respondString = Enum.GetName(typeof(TCPClient.ClientMessageType), TCPClient.ClientMessageType.OK);
                            Encoding.ASCII.GetBytes(respondString, 0, respondString.Length, respondbuffer, 0);
                            numBytesReply = respondString.Length + 1;
                        }
                        break;
                    case ServerRequestMessage.AskAttackerToAssignLosses:
                    case ServerRequestMessage.DetermineStrategicWarInterception:
                    case ServerRequestMessage.AskForNewCountryDeployment:
                    case ServerRequestMessage.AskForNavalInterception:
                    case ServerRequestMessage.ReportForNavalInterception:
                    case ServerRequestMessage.AskDefenderForDefenderOptions:
                    case ServerRequestMessage.AskDefenderForRetreatBeforeCombatMoves:
                    case ServerRequestMessage.AskForGSUCombatCasualties:
                    case ServerRequestMessage.DoDefenderCombat:
                    case ServerRequestMessage.DoDefenderBreakThroughCombat:
                    case ServerRequestMessage.AskDefenderToAssignLosses:
                    case ServerRequestMessage.AskDefenderForCounterAttack:
                        {
                            TCPClient.ClientMessageType clientMessageToReturn = TCPClient.ClientMessageType.AskForGSUCombatCasualtiesReturn;
                            string XmlString = Encoding.ASCII.GetString(receivebuffer, numBytesHeader + 1, bytesReceived - (numBytesHeader + 1));
                            XmlDocument xmlDoc = new XmlDocument();
                            xmlDoc.Load(XmlReader.Create(new System.IO.StringReader(XmlString)));
                            this.clientMessageReceived = ClientMessage.Deserialise(xmlDoc.FirstChild);
                            numBytesReply = 0;
                            switch (serverRequestMessage)
                            {
                                case ServerRequestMessage.DoDefenderCombat:
                                case ServerRequestMessage.AskAttackerToAssignLosses:
                                case ServerRequestMessage.DoDefenderBreakThroughCombat:
                                case ServerRequestMessage.AskDefenderToAssignLosses:
                                    clientMessageToReturn = TCPClient.ClientMessageType.AssignLossesReturn;
                                    break;
                                case ServerRequestMessage.AskForNavalInterception:
                                case ServerRequestMessage.DetermineStrategicWarInterception:
                                    clientMessageToReturn = TCPClient.ClientMessageType.boolReturn;
                                    break;
                                case ServerRequestMessage.AskForNewCountryDeployment:
                                    clientMessageToReturn = TCPClient.ClientMessageType.DeploymentMovesReturn;
                                    break;
                                case ServerRequestMessage.AskDefenderForDefenderOptions:
                                    clientMessageToReturn = TCPClient.ClientMessageType.AskForDefenderOptionsReturn;
                                    break;
                                case ServerRequestMessage.AskDefenderForRetreatBeforeCombatMoves:
                                    clientMessageToReturn = TCPClient.ClientMessageType.AskDefenderForRetreatBeforeCombatMovesReturn;
                                    break;
                                case ServerRequestMessage.AskForGSUCombatCasualties:
                                    clientMessageToReturn = TCPClient.ClientMessageType.AskForGSUCombatCasualtiesReturn;
                                    break;
                                case ServerRequestMessage.AskDefenderForCounterAttack:
                                    clientMessageToReturn = TCPClient.ClientMessageType.AskDefenderForCounterAttackReturn;
                                    break;
                            }
                            clientIsExpectingResponse = true;
                            ServerMessage serverMessageToReply = ((IGameControllerCaller)this.clientMessageReceived).CallGameController(currentGameController);
                            clientIsExpectingResponse = false;
                            respondString = Enum.GetName(typeof(TCPClient.ClientMessageType), clientMessageToReturn);
                            Encoding.ASCII.GetBytes(respondString, 0, respondString.Length, respondbuffer, 0);
                            numBytesReply = respondString.Length + 1;
                            XmlDocument serverMessageXML = new XmlDocument();
                            serverMessageXML.AppendChild(serverMessageToReply.Serialise(serverMessageXML));
                            System.IO.StringWriter sw = new System.IO.StringWriter();
                            XmlTextWriter xmlWriter = new XmlTextWriter(sw);
                            xmlWriter.Formatting = Formatting.None;
                            serverMessageXML.WriteTo(xmlWriter);
                            Encoding.ASCII.GetBytes(sw.ToString(), 0, sw.ToString().Length, respondbuffer, numBytesReply);
                            numBytesReply += sw.ToString().Length;
                        }
                        break;
                }

                System.IO.MemoryStream ms = new System.IO.MemoryStream(102400);
                GZipStream compressedzipStream = new GZipStream(ms, CompressionMode.Compress, true);
                compressedzipStream.Write(respondbuffer, 0, numBytesReply);
                compressedzipStream.Close();
                ClearRespondBuffer();
                ms.Position = 0;
                int numCompressedBytesReply = ReadAllBytesFromStream(ms, respondbuffer);
                respondbuffer[numCompressedBytesReply] = (byte)EndOfMessage;
                numCompressedBytesReply++;
                acceptedSocket.BeginSend(respondbuffer, 0, numCompressedBytesReply, SocketFlags.None, new AsyncCallback(EndSend), null);
            }
            catch(SocketException)
            {
                if(System.Windows.Forms.MessageBox.Show("The TCP connection to the client has been lost. Do you wish to save the current game?", "Europe Engulfed .NET", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
                {
                    EuropeEngulfedNETForm.MainForm.SaveGame();
                }
                exiting = true;
                System.Windows.Forms.Application.Exit();
            }
        }

        private void EndSend(IAsyncResult ar)
        {
            acceptedSocket.EndSend(ar);
            ClearReceiveBuffer();
            acceptedSocket.BeginReceive(receivebuffer, 0, receivebuffer.Length, SocketFlags.None, new AsyncCallback(EndReceive), null);
        }

        public void SetServerMessageToSend(ServerMessage serverMessage)
        {
            if(serverMessage.ServerMessageExpected != ServerRequestMessage.Polling)
                clientResponsesExpected.Add(serverMessage.ServerMessageExpected);
            if (clientIsExpectingResponse)
            {
                ServerMessage savedServerMessage = this.serverMessageToSend;
                this.serverMessageToSend = serverMessage;
                int numBytesReply = ProcessServerMessageRequest();
                System.IO.MemoryStream ms = new System.IO.MemoryStream(102400);
                GZipStream compressedzipStream = new GZipStream(ms, CompressionMode.Compress, true);
                compressedzipStream.Write(respondbuffer, 0, numBytesReply);
                compressedzipStream.Close();
                ClearRespondBuffer();
                ms.Position = 0;
                int numCompressedBytesReply = ReadAllBytesFromStream(ms, respondbuffer);
                respondbuffer[numCompressedBytesReply] = (byte)EndOfMessage;
                numCompressedBytesReply++;
                acceptedSocket.BeginSend(respondbuffer, 0, numCompressedBytesReply, SocketFlags.None, new AsyncCallback(EndSend), null);
                this.serverMessageToSend = savedServerMessage;
            }
            else
                this.serverMessageToSend = serverMessage;
        }
    }
}
